From c3349594403bd7d39a38fef0c2b73c449e651f49 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Sun, 1 Oct 2023 12:49:11 -0600 Subject: service: caps: Partially implement IAlbumAccessorService --- src/core/hle/service/caps/caps.cpp | 2 +- src/core/hle/service/caps/caps_a.cpp | 314 ++++++++++++++++++++++++++++++++++- src/core/hle/service/caps/caps_a.h | 118 ++++++++++++- src/core/hle/service/ns/ns.cpp | 27 ++- src/core/hle/service/ns/ns.h | 4 + 5 files changed, 450 insertions(+), 15 deletions(-) diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index 610fe9940..0dbf04862 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp @@ -16,7 +16,7 @@ namespace Service::Capture { void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("caps:a", std::make_shared(system)); + server_manager->RegisterNamedService("caps:a", std::make_shared(system)); server_manager->RegisterNamedService("caps:c", std::make_shared(system)); server_manager->RegisterNamedService("caps:u", std::make_shared(system)); server_manager->RegisterNamedService("caps:sc", std::make_shared(system)); diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index 44267b284..e1f836e08 100644 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp @@ -1,7 +1,14 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + +#include "common/fs/file.h" +#include "common/fs/path_util.h" #include "core/hle/service/caps/caps_a.h" +#include "core/hle/service/ipc_helpers.h" namespace Service::Capture { @@ -26,15 +33,16 @@ public: } }; -CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} { +IAlbumAccessorService::IAlbumAccessorService(Core::System& system_) + : ServiceFramework{system_, "caps:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAlbumFileCount"}, {1, nullptr, "GetAlbumFileList"}, {2, nullptr, "LoadAlbumFile"}, - {3, nullptr, "DeleteAlbumFile"}, + {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"}, {4, nullptr, "StorageCopyAlbumFile"}, - {5, nullptr, "IsAlbumMounted"}, + {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"}, {6, nullptr, "GetAlbumUsage"}, {7, nullptr, "GetAlbumFileSize"}, {8, nullptr, "LoadAlbumFileThumbnail"}, @@ -47,18 +55,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} { {15, nullptr, "GetAlbumUsage3"}, {16, nullptr, "GetAlbumMountResult"}, {17, nullptr, "GetAlbumUsage16"}, - {18, nullptr, "Unknown18"}, + {18, &IAlbumAccessorService::Unknown18, "Unknown18"}, {19, nullptr, "Unknown19"}, {100, nullptr, "GetAlbumFileCountEx0"}, - {101, nullptr, "GetAlbumFileListEx0"}, + {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"}, {202, nullptr, "SaveEditedScreenShot"}, {301, nullptr, "GetLastThumbnail"}, {302, nullptr, "GetLastOverlayMovieThumbnail"}, - {401, nullptr, "GetAutoSavingStorage"}, + {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"}, {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"}, {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"}, - {1002, nullptr, "LoadAlbumScreenShotImageEx1"}, - {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"}, + {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"}, + {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"}, {8001, nullptr, "ForceAlbumUnmounted"}, {8002, nullptr, "ResetAlbumMountStatus"}, {8011, nullptr, "RefreshAlbumCache"}, @@ -74,6 +82,294 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} { RegisterHandlers(functions); } -CAPS_A::~CAPS_A() = default; +IAlbumAccessorService::~IAlbumAccessorService() = default; + +void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto file_id{rp.PopRaw()}; + + LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}", + file_id.application_id, file_id.storage, file_id.type); + + if (file_id.storage == AlbumStorage::Sd) { + if (!Common::FS::RemoveFile(sd_image_paths[file_id.date.unique_id])) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage{rp.PopEnum()}; + + LOG_INFO(Service_Capture, "called, storage={}, is_mounted={}", storage, is_mounted); + + if (storage == AlbumStorage::Sd) { + FindScreenshots(); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_mounted); +} + +void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) { + struct UnknownBuffer { + INSERT_PADDING_BYTES(0x10); + }; + static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size"); + + LOG_WARNING(Service_Capture, "(STUBBED) called"); + + std::vector buffer{}; + + if (!buffer.empty()) { + ctx.WriteBuffer(buffer); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(buffer.size())); +} + +void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage{rp.PopEnum()}; + const auto flags{rp.Pop()}; + + LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags); + + std::vector entries{}; + + if (storage == AlbumStorage::Sd) { + AlbumEntry entry; + for (u8 i = 0; i < static_cast(sd_image_paths.size()); i++) { + if (GetAlbumEntry(entry, sd_image_paths[i]).IsError()) { + continue; + } + entry.file_id.date.unique_id = i; + entries.push_back(entry); + } + } + + if (!entries.empty()) { + ctx.WriteBuffer(entries); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(entries.size()); +} + +void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) { + bool is_autosaving{}; + + LOG_WARNING(Service_Capture, "(STUBBED) called, is_autosaving={}", is_autosaving); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_autosaving); +} + +void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto file_id{rp.PopRaw()}; + const auto decoder_options{rp.PopRaw()}; + + LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}", + file_id.application_id, file_id.storage, file_id.type, decoder_options.flags); + + const LoadAlbumScreenShotImageOutput image_output{ + .width = 1280, + .height = 720, + .attribute = + { + .unknown_0{}, + .orientation = ScreenShotOrientation::None, + .unknown_1{}, + .unknown_2{}, + }, + }; + + std::vector image(image_output.height * image_output.width * STBI_rgb_alpha); + + if (file_id.storage == AlbumStorage::Sd) { + LoadImage(image, sd_image_paths[file_id.date.unique_id], + static_cast(image_output.width), static_cast(image_output.height), + decoder_options.flags); + } + + ctx.WriteBuffer(image_output, 0); + ctx.WriteBuffer(image, 1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto file_id{rp.PopRaw()}; + const auto decoder_options{rp.PopRaw()}; + + LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}", + file_id.application_id, file_id.storage, file_id.type, decoder_options.flags); + + const LoadAlbumScreenShotImageOutput image_output{ + .width = 320, + .height = 180, + .attribute = + { + .unknown_0{}, + .orientation = ScreenShotOrientation::None, + .unknown_1{}, + .unknown_2{}, + }, + }; + + std::vector image(image_output.height * image_output.width * STBI_rgb_alpha); + + if (file_id.storage == AlbumStorage::Sd) { + LoadImage(image, sd_image_paths[file_id.date.unique_id], + static_cast(image_output.width), static_cast(image_output.height), + decoder_options.flags); + } + + ctx.WriteBuffer(image_output, 0); + ctx.WriteBuffer(image, 1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IAlbumAccessorService::FindScreenshots() { + is_mounted = false; + sd_image_paths.clear(); + + // TODO: Swap this with a blocking operation. + const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir); + Common::FS::IterateDirEntries( + screenshots_dir, + [this](const std::filesystem::path& full_path) { + AlbumEntry entry; + // TODO: Implement proper indexing to allow more images + if (sd_image_paths.size() > 0xFF) { + return true; + } + if (GetAlbumEntry(entry, full_path).IsSuccess()) { + sd_image_paths.push_back(full_path); + } + return true; + }, + Common::FS::DirEntryFilter::File); + + is_mounted = true; +} + +Result IAlbumAccessorService::GetAlbumEntry(AlbumEntry& out_entry, + const std::filesystem::path& path) { + std::istringstream line_stream(path.filename().string()); + std::string date; + std::string application; + std::string time; + + // Parse filename to obtain entry properties + std::getline(line_stream, application, '_'); + std::getline(line_stream, date, '_'); + std::getline(line_stream, time, '_'); + + std::istringstream date_stream(date); + std::istringstream time_stream(time); + std::string year; + std::string month; + std::string day; + std::string hour; + std::string minute; + std::string second; + + std::getline(date_stream, year, '-'); + std::getline(date_stream, month, '-'); + std::getline(date_stream, day, '-'); + + std::getline(time_stream, hour, '-'); + std::getline(time_stream, minute, '-'); + std::getline(time_stream, second, '-'); + + try { + out_entry = { + .entry_size = 1, + .file_id{ + .application_id = static_cast(std::stoll(application, 0, 16)), + .date = + { + .year = static_cast(std::stoi(year)), + .month = static_cast(std::stoi(month)), + .day = static_cast(std::stoi(day)), + .hour = static_cast(std::stoi(hour)), + .minute = static_cast(std::stoi(minute)), + .second = static_cast(std::stoi(second)), + .unique_id = 0, + }, + .storage = AlbumStorage::Sd, + .type = ContentType::Screenshot, + .unknown = 1, + }, + }; + } catch (const std::invalid_argument&) { + return ResultUnknown; + } catch (const std::out_of_range&) { + return ResultUnknown; + } catch (const std::exception&) { + return ResultUnknown; + } + + return ResultSuccess; +} + +Result IAlbumAccessorService::LoadImage(std::span out_image, const std::filesystem::path& path, + int width, int height, ScreenShotDecoderFlag flag) { + if (out_image.size() != static_cast(width * height * STBI_rgb_alpha)) { + return ResultUnknown; + } + + const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + std::vector raw_file(db_file.GetSize()); + if (db_file.Read(raw_file) != raw_file.size()) { + return ResultUnknown; + } + + int filter_flag = STBIR_FILTER_DEFAULT; + int original_width, original_height, color_channels; + const auto dbi_image = + stbi_load_from_memory(raw_file.data(), static_cast(raw_file.size()), &original_width, + &original_height, &color_channels, STBI_rgb_alpha); + + if (dbi_image == nullptr) { + return ResultUnknown; + } + + switch (flag) { + case ScreenShotDecoderFlag::EnableFancyUpsampling: + filter_flag = STBIR_FILTER_TRIANGLE; + break; + case ScreenShotDecoderFlag::EnableBlockSmoothing: + filter_flag = STBIR_FILTER_BOX; + break; + default: + filter_flag = STBIR_FILTER_DEFAULT; + break; + } + + stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width, + height, 0, STBI_rgb_alpha, 3, filter_flag); + + return ResultSuccess; +} } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h index 98a21a5ad..975c0ebdb 100644 --- a/src/core/hle/service/caps/caps_a.h +++ b/src/core/hle/service/caps/caps_a.h @@ -3,6 +3,7 @@ #pragma once +#include "common/fs/fs.h" #include "core/hle/service/service.h" namespace Core { @@ -11,10 +12,121 @@ class System; namespace Service::Capture { -class CAPS_A final : public ServiceFramework { +class IAlbumAccessorService final : public ServiceFramework { public: - explicit CAPS_A(Core::System& system_); - ~CAPS_A() override; + explicit IAlbumAccessorService(Core::System& system_); + ~IAlbumAccessorService() override; + +private: + enum class ContentType : u8 { + Screenshot, + Movie, + ExtraMovie, + }; + + enum class AlbumStorage : u8 { + Nand, + Sd, + + }; + + enum class ScreenShotDecoderFlag : u64 { + None = 0, + EnableFancyUpsampling = 1 << 0, + EnableBlockSmoothing = 1 << 1, + }; + + enum class ScreenShotOrientation : u32 { + None, + Rotate90, + Rotate180, + Rotate270, + }; + + struct ScreenShotAttribute { + u32 unknown_0; + ScreenShotOrientation orientation; + u32 unknown_1; + u32 unknown_2; + INSERT_PADDING_BYTES(0x30); + }; + static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size"); + + struct ScreenShotDecodeOption { + ScreenShotDecoderFlag flags; + INSERT_PADDING_BYTES(0x18); + }; + static_assert(sizeof(ScreenShotDecodeOption) == 0x20, + "ScreenShotDecodeOption is an invalid size"); + + struct AlbumFileDateTime { + u16 year; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + u8 unique_id; + }; + static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime is an invalid size"); + + struct AlbumFileId { + u64 application_id; + AlbumFileDateTime date; + AlbumStorage storage; + ContentType type; + INSERT_PADDING_BYTES(0x5); + u8 unknown; + }; + static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size"); + + struct AlbumEntry { + u64 entry_size; + AlbumFileId file_id; + }; + static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry is an invalid size"); + + struct ApplicationData { + std::array data; + u32 data_size; + }; + static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size"); + + struct LoadAlbumScreenShotImageOutput { + s64 width; + s64 height; + ScreenShotAttribute attribute; + INSERT_PADDING_BYTES(0x400); + }; + static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450, + "LoadAlbumScreenShotImageOutput is an invalid size"); + + struct LoadAlbumScreenShotImageOutputForApplication { + s64 width; + s64 height; + ScreenShotAttribute attribute; + ApplicationData data; + INSERT_PADDING_BYTES(0xAC); + }; + static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500, + "LoadAlbumScreenShotImageOutput is an invalid size"); + + void DeleteAlbumFile(HLERequestContext& ctx); + void IsAlbumMounted(HLERequestContext& ctx); + void Unknown18(HLERequestContext& ctx); + void GetAlbumFileListEx0(HLERequestContext& ctx); + void GetAutoSavingStorage(HLERequestContext& ctx); + void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx); + void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx); + +private: + void FindScreenshots(); + Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path); + Result LoadImage(std::span out_image, const std::filesystem::path& path, int width, + int height, ScreenShotDecoderFlag flag); + + bool is_mounted{}; + std::vector sd_image_paths{}; }; } // namespace Service::Capture diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 6e0baf0be..f9e0e272d 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -7,6 +7,7 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/vfs.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ns/errors.h" @@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_) static const FunctionInfo functions[] = { {11, nullptr, "CalculateApplicationOccupiedSize"}, {43, nullptr, "CheckSdCardMountStatus"}, - {47, nullptr, "GetTotalSpaceSize"}, - {48, nullptr, "GetFreeSpaceSize"}, + {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"}, + {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"}, {600, nullptr, "CountApplicationContentMeta"}, {601, nullptr, "ListApplicationContentMetaStatus"}, {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, @@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_) IContentManagementInterface::~IContentManagementInterface() = default; +void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage{rp.PopEnum()}; + + LOG_INFO(Service_Capture, "called, storage={}", storage); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(system.GetFileSystemController().GetTotalSpaceSize(storage)); +} + +void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage{rp.PopEnum()}; + + LOG_INFO(Service_Capture, "called, storage={}", storage); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(system.GetFileSystemController().GetFreeSpaceSize(storage)); +} + IDocumentInterface::IDocumentInterface(Core::System& system_) : ServiceFramework{system_, "IDocumentInterface"} { // clang-format off diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index 175dad780..34d2a45dc 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework { -- cgit v1.2.3